/**
 * Main class for the project. 
 */
import org.jscience.mathematics.number.*;
import java.util.*;
import java.math.*;
import java.io.*;
import Jama.*;

public class MainController implements Controller{
    
    static HashMap<Integer, User> users = new HashMap<Integer, User>();
    static HashMap<Integer,Player> players = new HashMap<Integer, Player>();
    static HashMap<Integer,Player> eliminated = new HashMap<Integer, Player>();
    static HIM M;
    static CircuitController cc;

    public static void main(String[] args){
        if(args.length != 3){
            System.err.println("Sorry - not enough arguments. I need 3 args: n mode #multGates");
            System.exit(-1);
        }
        int n = Integer.valueOf(args[0]);
        int mode = Integer.valueOf(args[1]);
        int multGates = Integer.valueOf(args[2]);
        //Doing some math as to how many triples needs generating. 
        Utilities.update(n, mode, multGates);
        
        System.out.println("Field Size |F| became: " + Utilities.F);
        System.out.println("n: " + Utilities.n + ", t: " + Utilities.t + ", T: "+ Utilities.T);
        System.out.println("tripleAmount: " + Utilities.tripleAmount + 
                           ", l: " + Utilities.l + ", genTripAmount: "+ Utilities.genTripAmount);
        System.out.println("");

        //Setting modulus for the LUDecomposition 
        ModuloInteger.setModulus(new LargeInteger(Utilities.F));

        //Initialize and construct Hyper-invertible Matrix M
        System.out.println("The hyper-invertible matrix: ");
        M = new HIM();
        M.constructHIM();
        System.out.println("");

        //Initialize the circuit controller:
        int ccArg = Integer.valueOf(mode);
        cc = new CircuitController(ccArg, multGates);
        cc.start();
        System.out.println("Circuit controller started. ");
        System.out.println("");

        //Initialize n users and players - users first is important.
        initPlayers();    
        initUsers();
        System.out.println("");

        cc.setPlayers(players);
        cc.setUsers(users);
        System.out.println("Circuit controller initialized with the "+Utilities.n+" players.");

 //===============================  preparation phase ===================================
        //start timer
        Utilities.time = System.currentTimeMillis();

        //Get players to generateTriples
        noOfGenTrips++;
        for(Integer i : players.keySet()){
            players.get(i).generateTriples();            
        }

        //Start making manual testing
        help();        
        readInput();
    }
    static int noOfGenTrips = 0; //used in faultlocalization

    public int getNoOfGenTrips(){
        return noOfGenTrips;
    }

    static int count = 0, loops = 0, segment = 0;

    public void nextSegment(){
        count++;
        boolean next = false;
        if(count % Utilities.n_prime == 0){
            loops++;
            if(loops < Utilities.genTripAmount){ 
                noOfGenTrips++;
                for(Integer i: players.keySet()){
                    System.out.println("Making player "+ i + " generate triples...");
                    players.get(i).generateTriples();            
                }
            }
            else{
                next = true;
            }
        }
        if(next){
            System.out.println("Segment done...");
            count = 0;
            next = false;
            loops = -1;
            checkHappyStatus();                        
        }
    }

    public static void checkHappyStatus(){
        int unhappyCount = 0;
        for(Integer i : players.keySet()){
            Player p = players.get(i);
            p.faultDetection();
        }
    }

    HashMap<Integer, Boolean> hap = new HashMap<Integer, Boolean>();

    public void returnHappyness(int sender, boolean happy){
        hap.put(sender, happy);
        if(hap.size() == Utilities.n_prime){
            hap.clear();
            System.out.println("Happy consensus made.. Players where happy?: " + happy);
            if(happy){
                segment++;
                if(segment != Utilities.t && Utilities.t != 0){
                    loops = 0;
                    count = 0;
                    System.out.println("Doing next segment");
                    noOfGenTrips++;
                    for(Integer i: players.keySet()){
                        players.get(i).generateTriples();
                    }
                }
                else{
                    //Okay - now let's try do some computation phase. There is
                    //still no corruption present...
                    System.out.println("");
                    System.out.println("Done with preparation phase. Going over to computation phase:");
                    System.out.println("");
                    time(true);
                    cc.runCircuit();
                }
            }
            else{
                //Player elimination framework initializing
                System.out.println("Okay - at least one honest player was unhappy. "+
                                   "We therefore start player elimination starting "+
                                   "with fault localization.");
                for(Integer i: players.keySet()){
                    players.get(i).faultLocalization();
                }
            }
        }
        
    }

    public static void bytesSend(){
        int total = 0;
        for(Integer i: players.keySet()){
            total += players.get(i).totalBytesSend();
        }
        for(Integer i: users.keySet()){
            total += users.get(i).totalBytesSend();
        }
        for(Integer i: eliminated.keySet()){
            total += eliminated.get(i).totalBytesSend();
        }
        System.out.println("Bytes send in this phase: " + total);
        readInput();
        //shutdown();
    }

    int prepBytes=0;
    int compBytes=0;
    public void bytesSendPhase(boolean preparation){
        int total = 0;
        for(Integer i: players.keySet()){
            total += players.get(i).totalBytesSend();
            players.get(i).totalBytesSend = 0;
        }
        for(Integer i: users.keySet()){
            total += users.get(i).totalBytesSend();
            users.get(i).totalBytesSend = 0;
        }
        for(Integer i: eliminated.keySet()){
            total += eliminated.get(i).totalBytesSend();
            eliminated.get(i).totalBytesSend = 0;
        }
        System.out.println("Bytes send in this phase: " + total);
        if(preparation)
            prepBytes = total;
        else
            compBytes = total;
    }
    
    long prepTime, compTime;
    public void time(boolean preparation){
        if(preparation){
            System.out.println("Done with preparation phase. Time and bytes Send so far:");
            long endTime = System.currentTimeMillis();
            long totalTime = endTime-Utilities.time;
            prepTime = totalTime;
            System.out.println("Protocol spend " + totalTime + " milliseconds so far");
            bytesSendPhase(preparation);
            System.out.println();            
        }
        else{
            System.out.println("Done with computation phase. Time and bytes Send in this phase only:");
            long endTime = System.currentTimeMillis()-Utilities.time;
            long totalTime = endTime-prepTime;
            compTime = totalTime;
            System.out.println("Protocol spend " + totalTime + " milliseconds in compPhase");
            bytesSendPhase(preparation);
            System.out.println();
        }
        if(!preparation){
            //at the end of the computation - need total time and bytes also
            System.out.println("Protocol ended after spending " + (prepTime+compTime) + " milliseconds");
            System.out.println("And the protocol spend a total of " + (prepBytes+compBytes) + " bytes");
            shutdown();
        }
    }

    HashMap<Integer, Integer> eliminationReady = new HashMap<Integer, Integer>();
    /**
     * Method for telling controller that it was {p1,p2} who was
     * eliminated such that it can update accordingly.
     */
    public void playerElimination(int sender, int p1, int p2){
        eliminationReady.put(sender, sender);
        if(eliminationReady.size() == Utilities.n_prime){
            eliminationReady.clear();
            Utilities.playersEliminated();
            //TODO: restart the segment or say we are done if t'=0.
            if(Utilities.t_prime == 0){
                System.out.println("");
                System.out.println("We have now eliminated all and every corrupted player. "+
                                   "Thus, we are now free to send stuff along without fear."+
                                   " We are done.. The honest people won!");                

                long endTime = System.currentTimeMillis();
                long totalTime = endTime-Utilities.time;
                System.out.println("Protocol ended after spending " + totalTime + " milliseconds");
                bytesSend();

                return;
            }
            Player first = players.remove(p1);
            eliminated.put(p1, first);
            Player second = players.remove(p2);
            eliminated.put(p2, second);
            first.shutdown();
            second.shutdown();
            System.out.println("Testing player elimination... player size from main: " + players.size());
            //recreate the Hyper-invertible matrix - might not be the right way to do it. 
            //M = new HIM();
            //M.constructHIM();        

            System.out.println("");
            System.out.println("Restarting the segment...");
            System.out.println("");

            //reset server such that we are back in a 1,..,n' situation
            for(Integer i: players.keySet()){
                players.get(i).resetServer();
            }
        }
    }

    public static void readInput(){
        BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));

        String input = "";
        try{
            input = buf.readLine();
        }
        catch(IOException e){
            e.printStackTrace();
        }
        String[] split = input.split(" ");        
        if(split.length == 1){
            if(split[0].equals("exit")){
                System.out.println("Terminating program");
                shutdown();
            }
            else if(split[0].equals("help")){
                help();
                readInput();
            }
            else if(split[0].equals("bytesSend")){
                bytesSend();
            }
            else{
                System.out.println("Command not understood. Try again");
                readInput();
            }
        }
        try{
            if(split.length == 2){
                if(split[0].equals("bytesSend")){
                    int p1 = Integer.valueOf(split[1]);
                    if(p1 > 0 || p1 < Utilities.n){
                        int bytesSend = players.get(p1).totalBytesSend();
                        System.out.println("Player " + p1 + " has send " + 
                                           bytesSend + " so far.");
                        readInput();
                    }
                    else{
                        System.out.println("Command not understood. Try again");
                        readInput();
                    }
                }
            }
            else{
                help();
                readInput();
            }
        }
        catch(NumberFormatException e){
            System.out.println("Entered something that should have been a number. Try again");
            readInput();
        }
        System.out.println("Command not understood. Try again");
        readInput();
    }

    public static void help(){
        System.out.println("Usage: ");
        System.out.println("       help: help menu");
        System.out.println("       exit: exits the program");
        System.out.println("       bytesSend: Check total bytes sent.");
    }

    public static void initUsers(){
        User u;
        for(int i = 1; i<= Utilities.n; i++){
            //set up for ID=input. If we want something else, just
            //enter as param.
            u = new User(i, i, players.get(i), players);
            u.start();
            users.put(i, u);
        }

        HashMap<Integer, Integer> copyOfUsers = new HashMap<Integer, Integer>();
        for(Integer i: users.keySet()){
            copyOfUsers.put(i,i);
        }
        for(Integer i: users.keySet()){
            users.get(i).init(copyOfUsers);
        }

    }

    public static void initPlayers(){
        Player p;
        Controller main = new MainController(); //for the players..
        for(int i = 1; i <= Utilities.n ; i++){
            if(i > (Utilities.n-Utilities.t))
                p = new Player(i, M, main, cc, true);
            else 
                p = new Player(i, M, main, cc, false);
            p.start();
            players.put(i,p);
        }
        HashMap<Integer, Integer> copyOfPlayers = new HashMap<Integer, Integer>();
        for(Integer i: players.keySet()){
            copyOfPlayers.put(i,i);
        }
        for(int i = 1; i <= Utilities.n ; i++){
            p = players.get(i);
            p.initPlayer(copyOfPlayers);
            if(p.corrupt)
                System.out.println("Player " + p.getID() + " is now initialized   ---- corrupt");
            else
                System.out.println("Player " + p.getID() + " is now initialized");
        }
    }

    public static void shutdown(){
        for(Integer i : players.keySet()){
            Player p = players.get(i);
            p.shutdown();            
        }
        System.exit(0);
    }
}